#include "PictureBus.h"
#include "Log.h"

PNE_NAMESPACE_BEGIN

PictureBus::PictureBus() : m_palatte(0x20),
                           m_RAM(0x800),
                           m_mapper(nullptr)
{
}

Byte PictureBus::Read(Address addr)
{
    if (addr < 0x2000)
        return m_mapper->ReadCHR(addr);
    else if (addr < 0x3eff)
    {
        const auto index = addr & 0x3ff;
        // Name tables upto 0x3000, then mirrored upto 3eff
        auto normalizedAddr = addr;
        if (addr >= 0x3000)
            normalizedAddr -= 0x1000;

        if (NameTable0 >= m_RAM.size())
            return m_mapper->ReadCHR(normalizedAddr);
        else if (normalizedAddr < 0x2400) // NT0
            return m_RAM[NameTable0 + index];
        else if (normalizedAddr < 0x2800) // NT1
            return m_RAM[NameTable1 + index];
        else if (normalizedAddr < 0x2c00) // NT2
            return m_RAM[NameTable2 + index];
        else // if (normalizedAddr < 0x3000) // NT3
            return m_RAM[NameTable3 + index];
    }
    else if (addr < 0x3fff)
    {
        auto paletteAddr = addr & 0x1f;
        return ReadPalette(paletteAddr);
    }

    return 0;
}

Byte PictureBus::ReadPalette(Byte paletteAddr)
{
    // Addresses $3F10/$3F14/$3F18/$3F1C are mirrors of $3F00/$3F04/$3F08/$3F0C
    if (paletteAddr >= 0x10 && paletteAddr % 4 == 0)
        paletteAddr = paletteAddr & 0xf;
    return m_palatte[paletteAddr];
}

void PictureBus::Write(Address addr, Byte value)
{
    if (addr < 0x2000)
        m_mapper->WriteCHR(addr, value);
    else if (addr < 0x3eff)
    {
        const auto index = addr & 0x3ff;
        // Name tables upto 0x3000, then mirrored upto 3eff
        auto normalizedAddr = addr;
        if (addr >= 0x3000)
            normalizedAddr -= 0x1000;

        if (NameTable0 >= m_RAM.size())
            m_mapper->WriteCHR(normalizedAddr, value);
        else if (normalizedAddr < 0x2400)
            m_RAM[NameTable0 + index] = value;
        else if (normalizedAddr < 0x2800)
            m_RAM[NameTable1 + index] = value;
        else if (normalizedAddr < 0x2c00)
            m_RAM[NameTable2 + index] = value;
        else
            m_RAM[NameTable3 + index] = value;
    }
    else if (addr < 0x3fff)
    {
        auto palette = addr & 0x1f;
        if (palette >= 0x10 && addr % 4 == 0)
        {
            palette = palette & 0xf;
        }
        m_palatte[palette] = value;
    }
}

void PictureBus::UpdateMirroring()
{
    switch (m_mapper->GetNameTableMirroring())
    {
    case NameTableMirroring::Horizontal:
        NameTable0 = NameTable1 = 0;
        NameTable2 = NameTable3 = 0x4000;
        LOG(Level::InfoVerbose) << "Horizontal Name Table mirroring set. (Vertical Scrolling)" << std::endl;
        break;
    case NameTableMirroring::Vertical:
        NameTable0 = NameTable2 = 0;
        NameTable1 = NameTable3 = 0x400;
        LOG(Level::InfoVerbose) << "Vertical Name Table mirroring set. (Horizontall Scrolling)" << std::endl;
        break;

    case NameTableMirroring::OneScreenLower:
        NameTable0 = NameTable1 = NameTable2 = NameTable3 = 0;
        LOG(Level::InfoVerbose) << "Single Screen mirroring set with lower bank." << std::endl;
        break;
    case NameTableMirroring::OneScreenHigher:
        NameTable0 = NameTable1 = NameTable2 = NameTable3 = 0x400;
        LOG(Level::InfoVerbose) << "Single Screen mirroring set with higher bank." << std::endl;
        break;
    case NameTableMirroring::FourScreen:
        NameTable0 = m_RAM.size();
        LOG(Level::InfoVerbose) << "FourScreen mirroring." << std::endl;
        break;
    default:
        NameTable0 = NameTable1 = NameTable2 = NameTable3 = 0;
        LOG(Level::Error) << "Unsupported Name Table mirroring : " << static_cast<int>(m_mapper->GetNameTableMirroring()) << std::endl;
    }
}

bool PictureBus::SetMapper(Mapper *mapper)
{
    if (!mapper)
    {
        LOG(Level::Error) << "Mapper argument is nullptr" << std::endl;
        return false;
    }
    m_mapper = mapper;
    UpdateMirroring();
    return true;
}

void PictureBus::ScanlineIRQ()
{
    m_mapper->ScanLineIRQ();
}

PNE_NAMESPACE_END
// DONE